﻿// Class1.cpp
#include "pch.h"
#include "DXContext.h"
#include "Factories.h"
#include "Helper.h"
#include "ErrorCodes.h"
#include "DXAdapter.h"
#include "Texture2D.h"
#include "States.h"
#include "primitives.h"

using namespace Platform;
using namespace Platform::Collections;
using namespace concurrency;

using namespace DirectX;
using namespace D2D1;

using namespace Microsoft::WRL;
using namespace Windows::UI::Core;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;

using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::System;
using namespace Windows::UI::ViewManagement;
using namespace Windows::UI::Xaml::Media::Imaging;

using namespace DXBase;
using namespace DXBase::D3D;

XMFLOAT4X4 DXBase::GetRotationMatrix(DisplayOrientations orientation)
{
	switch (orientation)
	{
		case DisplayOrientations::Landscape:
			return XMFLOAT4X4( // 0-degree Z-rotation
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;
		case DisplayOrientations::Portrait:
			return XMFLOAT4X4( // 90-degree Z-rotation
				0.0f, 1.0f, 0.0f, 0.0f,
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;
		case DisplayOrientations::LandscapeFlipped:
			return XMFLOAT4X4( // 180-degree Z-rotation
				-1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, -1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;
		case DisplayOrientations::PortraitFlipped:
			return XMFLOAT4X4( // 270-degree Z-rotation
				0.0f, -1.0f, 0.0f, 0.0f,
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f
				);
			break;
		default:
			throw ref new Platform::FailureException();
	}
}
DXGI_MODE_ROTATION DXBase::GetRotationMode(DisplayOrientations orientation)
{
	switch (orientation)
	{
		case DisplayOrientations::Landscape:
			return DXGI_MODE_ROTATION_IDENTITY;
		case DisplayOrientations::Portrait:
			return DXGI_MODE_ROTATION_ROTATE270;
		case DisplayOrientations::LandscapeFlipped:
			return DXGI_MODE_ROTATION_ROTATE180;
		case DisplayOrientations::PortraitFlipped:
			return DXGI_MODE_ROTATION_ROTATE90;
		default:
			throw ref new Platform::FailureException();
	}
}
static inline USize OrientSize(USize size, DisplayOrientations orient)
{
	bool swap = orient == DisplayOrientations::Portrait || orient == DisplayOrientations::PortraitFlipped;
	if (swap)
	{
		auto w = size.width;
		size.width = size.height;
		size.height = w;
	}
	return size;
}


void NativeRenderTarget::Initialize(DXContext^ ctxt, IRenderTarget^ target)
{
	uSize = target->PixelSize;

	// create render view
	HR2Ex(ctxt->m_d3dDevice->CreateRenderTargetView(m_renderTarget.Get(), nullptr, &m_renderTargetView));

	// Create depth/stencil buffer descriptor.
	CD3D11_TEXTURE2D_DESC depthStencilDesc(
		DXGI_FORMAT_D24_UNORM_S8_UINT, 
		uSize.width,
		uSize.height,
		1,
		1,
		D3D11_BIND_DEPTH_STENCIL
		);

	// Allocate a 2-D surface as the depth/stencil buffer.
	HR2Ex(ctxt->m_d3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, &m_depthStencil));

	// Create depth/stencil view based on depth/stencil buffer.
	const CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc = CD3D11_DEPTH_STENCIL_VIEW_DESC(D3D11_DSV_DIMENSION_TEXTURE2D);    
	HR2Ex(ctxt->m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &depthStencilViewDesc, &m_depthStencilView));

	// setup D2D
	ComPtr<IDXGISurface> dxgitarget;
	HR2Ex(m_renderTarget.As(&dxgitarget));
	HR2Ex(ctxt->m_d2dContext->CreateBitmapFromDxgiSurface(dxgitarget.Get(), nullptr, &d2dtarget));

    // Grayscale text anti-aliasing is recommended for all Metro style apps.
    ctxt->m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
	ctxt->m_d2dContext->SetDpi(target->Dpi, target->Dpi);

	// set initialized to true
	initialized = true;
}


DXContext::DXContext() : debug(L"DXContext")
{
	Init();
}
DXContext::DXContext(DXAdapter^ ada) : debug(L"DXContext")
{
	mAdapter = ada;
	Init();
}
void DXContext::Init()
{
	vpSize.width = vpSize.height;
	mStatus = ContextStatus::NoDevice;
	CreateDeviceResources();
}
void DXContext::Target::set(IRenderTarget^ value)
{
	if (value == Target)
		return;

	if (Rendering) 
		Flush();

	mTarget = value;
	if (value)
		mTarget->Context = this;
	UpdateNativeTarget();

	OnPropertyChanged(L"Target");
	OnPropertyChanged(L"PixelSize");
}

void DXContext::CreateDeviceResources()
{
	// This flag adds support for surfaces with a different color channel ordering
	// than the API default. It is required for compatibility with Direct2D.
	UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(_DEBUG)
	// If the project is in a debug build, enable debugging via SDK Layers with this flag.
	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// This array defines the set of DirectX hardware feature levels this app will support.
	// Note the ordering should be preserved.
	// Don't forget to declare your application's minimum required feature level in its
	// description.  All applications are assumed to support 9.1 unless otherwise stated.
	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1
	};

	// Create the Direct3D 11 API device object and a corresponding context.
	ComPtr<ID3D11Device> device;
	ComPtr<ID3D11DeviceContext> context;
	HR2Ex(
		D3D11CreateDevice(
			(mAdapter ? mAdapter->mAdapter.Get() : nullptr), // Specify nullptr to use the default adapter.
			D3D_DRIVER_TYPE_HARDWARE,
			nullptr,
			creationFlags, // Set set debug and Direct2D compatibility flags.
			featureLevels, // List of feature levels this app can support.
			ARRAYSIZE(featureLevels),
			D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
			&device, // Returns the Direct3D device created.
			&m_featureLevel, // Returns feature level of device created.
			&context // Returns the device immediate context.
			)
		);

	if (!mAdapter) {
		ComPtr<IDXGIDevice1>  dxgiDevice;
		HR2Ex(device.As(&dxgiDevice));
		ComPtr<IDXGIAdapter> dxgiAdapter;
		HR2Ex(dxgiDevice->GetAdapter(&dxgiAdapter));
		mAdapter = ref new DXAdapter(dxgiAdapter);
	}

	// Get the Direct3D 11.1 API device and context interfaces.
	HR2Ex(device.As(&m_d3dDevice));
	HR2Ex(context.As(&m_d3dContext));

    // Get the underlying DXGI device of the Direct3D device.
    ComPtr<IDXGIDevice> dxgiDevice;
    HR2Ex(m_d3dDevice.As(&dxgiDevice));

	// old method
		//// Create the Direct2D device object and a corresponding context.
		//HR2Ex(D2D1CreateDevice(dxgiDevice.Get(), nullptr, &m_d2dDevice));
		//// REMARK get the factory associated with the device, is necessary for some API
		//// http://social.msdn.microsoft.com/Forums/en/winappswithnativecode/thread/ee8438c5-240a-408b-af7e-024b1d23e8c1
		//ComPtr<ID2D1Factory> factory;
		//m_d2dDevice->GetFactory(factory.GetAddressOf());
		//HR2Ex(factory.As(&m_d2dFactory));
	// new method
	HR2Ex(Factories::GetD2DFactory()->CreateDevice(dxgiDevice.Get(), &m_d2dDevice));


	HR2Ex(m_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, &m_d2dContext));

	vpSize.width = vpSize.height; // be sure to update viewport

	// inform listener of the operation
	Status = ContextStatus::CreatingDevice;
}
// check the device is valid, recreate it if not...
bool DXContext::ValidateDevice()
{
	HRESULT hr = m_d3dDevice->GetDeviceRemovedReason();
	if (FAILED(hr))
	{
		CreateDeviceResources();
		return false;
	}
	return true;
}
// this is called before each draw, only initialize which needs to
void DXContext::UpdateNativeTarget()
{
	nativeTarget = nullptr;
	if (!Target)
	{
		m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
		m_d2dContext->SetTarget(nullptr);
		return;
	}

	//auto CW = Windows::UI::Xaml::Window::Current->CoreWindow;
	Target->Context = this;
	nativeTarget = Target->GetNativeTarget();
	if (!nativeTarget)
	{
		m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
		m_d2dContext->SetTarget(nullptr);
		return;
	}

	if (!nativeTarget->initialized)
	{
		nativeTarget->Initialize(this, Target);
		Status = ContextStatus::CreatingTargetResources;
	}
	if (vpSize != nativeTarget->uSize)
	{
		vpSize = nativeTarget->uSize;
		box3d vp = {0, 0, 0, (float)vpSize.width, (float)vpSize.height, 1};
		Viewport = vp;
	}

	// update every time ...
	m_d3dContext->OMSetRenderTargets(1, nativeTarget->m_renderTargetView.GetAddressOf(), nativeTarget->m_depthStencilView.Get());
	m_d2dContext->SetTarget(nativeTarget->d2dtarget.Get());
	m_d2dContext->SetDpi(Target->Dpi, Target->Dpi);

	Status = ContextStatus::Ready;
}

box3d DXContext::Viewport::get()
{
	if(!m_d3dContext)
		return box3d();
	D3D11_VIEWPORT vp;
	UINT num = 1;
	m_d3dContext->RSGetViewports(&num, &vp);
	return fromD3D(vp);
}
static bool UpdateVP(box3d& value, const USize& pixels)
{
	float x0 = max(0, value.x);
	float x1 = min(pixels.width, value.x + value.width);
	float y0 = max(0, value.y);
	float y1 = min(pixels.height, value.y + value.height);
	float z0 = max(0, value.z);
	float z1 = min(1, value.z + value.depth);
	value.x = x0;
	value.width = x1 - x0;
	value.y = y0;
	value.height = y1 - y0;
	value.z = z0;
	value.depth = z1 - z0;
	return value.width > 0 && value.height > 0 && value.depth > 0;
}
void DXContext::Viewport::set(box3d value)
{
	if (!Target)
		return;
	auto ps = Target->PixelSize;
	if (!::UpdateVP(value, ps))
		throw ref new InvalidArgumentException();

	if(!m_d3dContext)
		return;

	auto vp = makeD3D(value);
	auto offset = Target->ViewportOffset;
	vp.TopLeftX += offset.width;
	vp.TopLeftY += offset.height;

	m_d3dContext->RSSetViewports(1, &vp);
}
IVector<box3d>^ DXContext::Viewports::get()
{
	auto res = ref new Vector<box3d>();
	if(!m_d3dContext)
		return res;

	UINT num;
	m_d3dContext->RSGetViewports(&num, NULL);

	std::vector<D3D11_VIEWPORT> vps;
	vps.resize(num);
	m_d3dContext->RSGetViewports(&num, &vps[0]);

	for (size_t i=0; i<vps.size(); i++)
		res->Append(fromD3D(vps[i]));
	return res;
}
void DXContext::Viewports::set(IVector<box3d>^ values)
{
	if (!Target)
		return;
	auto ps = Target->PixelSize;
	std::vector<D3D11_VIEWPORT> vps;
	for each (box3d box in values)
	{
		if (!::UpdateVP(box, ps))
			throw ref new InvalidArgumentException();
		vps.push_back(makeD3D(box));
	}
	m_d3dContext->RSSetViewports(vps.size(), &vps[0]);
}

// ============================== DRAWING

void DXContext::Clear(Windows::UI::Color bg)
{
	if (!Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);

	float fcolor[4];
	DXBase::makeD3D(bg, fcolor);
	m_d3dContext->ClearRenderTargetView(nativeTarget->m_renderTargetView.Get(), fcolor);
	//m_d2dContext->Clear(makeD2D(bg));
}
void DXContext::Flush()
{
	if (!Rendering)
		return;
	m_d3dContext->Flush();
	HR2Ex(m_d2dContext->Flush());
}

bool DXContext::BeginRender()
{
	if (Status != ContextStatus::Ready)
		throw ExHelper::CreateException(ErrorCodes::ContextNotReady);

	if(!Target)
		return false;
	UpdateNativeTarget();
	if (!nativeTarget)
		return false;

	m_d3dContext->ClearDepthStencilView(nativeTarget->m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
    m_d2dContext->BeginDraw();
	return true;
}

// Method to deliver the final image to the display.
void DXContext::EndRender()
{
	Flush();
	HR2Ex(m_d2dContext->EndDraw());
	Target->EndRender();
	nativeTarget = nullptr;
}


//===============  ContextWatcher  ======================

IAsyncOperation<bool>^ ContextWatcher::finishedSuccess = create_async([] { return true;  });

ContextWatcher::ContextWatcher()
{
}
ContextWatcher::~ContextWatcher()
{
	Context = nullptr;
	Target = nullptr;
}

DXContext^ ContextWatcher::Context::get() { return mContext; }
void ContextWatcher::Context::set(DXContext^ ctxt)
{
	if (mContext == ctxt)
		return;

	UnregisterEvent();
	mContext = ctxt;
	RegisterEvent();

	auto target = mWeakTarget.Resolve<IContextBound>();
	if (target)
		target->ClearDXData();
}
IContextBound^ ContextWatcher::Target::get() { return mWeakTarget.Resolve<IContextBound>(); }
void ContextWatcher::Target::set(IContextBound^ target)
{
	if (target)
	{
		mWeakTarget = target; // WeakReference(target);
		target->ClearDXData();
	}
	else
	{
		mWeakTarget = WeakReference();
	}
}
void ContextWatcher::RegisterEvent()
{
	if (mWatching || !mContext)
		return;
	auto w = Windows::UI::Core::CoreWindow::GetForCurrentThread();
	if (!w)
		return;
	if (w->Dispatcher->HasThreadAccess) {
		auto handler = ref new ContextStatusChangedEventHandler(this, &ContextWatcher::OnContextReset);
		eventtoken = mContext->ContextReset += handler;
		mWatching = true;
	}
	else {
		w->Dispatcher->RunAsync(CoreDispatcherPriority::Low, 
			ref new Windows::UI::Core::DispatchedHandler([this]() -> void
			{
				RegisterEvent();
			})
		);
	}
}
void ContextWatcher::UnregisterEvent()
{
	if (!mWatching)
		return;
	mContext->ContextReset -= eventtoken;
	mWatching = false;
	mContext = nullptr;
}
void ContextWatcher::OnContextReset(DXContext^ sender, ContextStatus status)
{
	auto target = Target;
	if (target)
		target->ClearDXData();
}

